<div id="app"></div>
<script>
    //vdom
    function h(tag, props, children) {
        return {
            tag,
            props,
            children
        }
    }

    function mount(vnode, container) {
        const el = vnode.el = document.createElement(vnode.tag)
        if (vnode.props) {
            for (const key in vnode.props) {
                const value = vnode.props[key];
                if (key.startsWith('on')) {
                    el.addEventListener(key.slice(2).toLowerCase(), value)
                } else {
                    el.setAttribute(key, value)
                }

            }
        }
        if (vnode.children) {
            if (typeof vnode.children === 'string') {
                el.textContent = vnode.children
            } else {
                vnode.children.forEach(child => {
                    mount(child, el)
                });
            }

        }
        container.appendChild(el)
    }

    function patch(n1, n2) {
        const el = n2.el = n1.el
        if (n1.tag === n2.tag) {
            const oldProps = n1.props || {}
            const newProps = n2.props || {}
            for (const key in newProps) {
                const oldValue = oldProps[key]
                const newValue = newProps[key]
                if (newValue !== oldValue) {
                    el.setAttribute(key, newValue)
                }
            }
            //考虑到一种情况,当新的el上并没旧元素上的props时,要删除
            for (const key in oldProps) {
                if (!(key in newProps)) {
                    el.removeAttribute(key)
                }
            }


            //children
            const oldChildren = n1.children
            const newChildren = n2.children
            if (typeof newChildren === 'string') {
                if (typeof oldChildren === 'string') {
                    if (newChildren !== oldChildren) {
                        el.textContent = newChildren
                    }
                } else {
                    el.textContent = newChildren
                }
            } else {
                if (typeof oldChildren === 'string') {
                    el.innweHTML = ''
                    newChildren.forEach(child => {
                        mount(child, el)
                    })
                } else {
                    const commonLength = Math.min(oldChildren.length, newChildren.length)
                    for (let index = 0; index < commonLength; index++) {
                        patch(oldChildren[index], newChildren[index])
                    }
                    if (newChildren.length > oldChildren.length) {
                        newChildren.slice(oldChildren.length).forEach(child => {
                            mount(child, el)
                        })
                    } else if (newChildren.length < oldChildren.length) {
                        oldChildren.slice(newChildren.length).forEach(child => {
                            el.removeChild(child, el)
                        })
                    }
                }
            }



        } else {
            //replace
        }

    }

    //reactive
    let activeEffect
    class Dep {
        subscribers = new Set()

        depend() {
            if (activeEffect) {
                this.subscribers.add(activeEffect)
            }
        }
        notify() {
            this.subscribers.forEach(effect => {
                effect()
            })
        }
    }

    const targetMap = new WeakMap()

    function getDep(target, key) {
        let depsMap = targetMap.get(target)
        if (!depsMap) {
            depsMap = new Map()
            targetMap.set(target, depsMap)
        }
        let dep = depsMap.get(key)
        if (!dep) {
            dep = new Dep()
            depsMap.set(key, dep)
        }
        return dep
    }

    const reactiveHandelers = {
        get(target, key, reactiver) {
            const dep = new getDep(target, key)
            dep.depend()
            return Reflect.get(target, key, reactiver)
        },
        set(target, key, value, reactiver) {
            const dep = new getDep(target, key)
            let res = Reflect.set(target, key, value, reactiver)
            dep.notify()
            return res
        }
    }

    function reactive(raw) {
        /* ES 5方法*/
        // Object.keys(raw).forEach(key => {
        //     const dep = new Dep()
        //     let value = raw[key]
        //     Object.defineProperty(raw, key, {
        //         get() {
        //             dep.depend()
        //             return value
        //         },
        //         set(newValue) {
        //             value = newValue
        //             dep.notify()
        //         }
        //     })
        // })
        // return raw
        /*ES6方法*/
        return new Proxy(raw, reactiveHandelers)
    }

    function watchEffect(effect) {
        activeEffect = effect
        effect()
        activeEffect = null
    }

    const App = {
        data: reactive({
            count: 0
        }),
        render() {
            return h('div', {
                onClick: () => {
                    this.data.count++
                }
            }, String(this.data.count))
        },
    }

    function mountApp(component, container) {
        let isMounted = false
        let prevVom
        watchEffect(() => {
            if (!isMounted) {
                prevVom = component.render()
                mount(prevVom, container)
                isMounted = true
            } else {
                const newVdom = component.render()
                patch(prevVom, newVdom)
                prevVom = newVdom
            }
        })
    }
    mountApp(App, document.getElementById('app'))
</script>